﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Aphysoft.Vate.Graphics
{
    public class Terrain
    {
        #region Properties
        /// <summary>
        /// Vertex count x.
        /// </summary>
        private int vertexCountX = -1;
        /// <summary>
        /// Gets vertex count in x-axis.
        /// </summary>
        public int VertexCountX
        {
            get { return vertexCountX; }
        }
        /// <summary>
        /// Vertex count z.
        /// </summary>
        private int vertexCountZ = -1;
        /// <summary>
        /// Gets vertex count in z-axis.
        /// </summary>
        public int VertexCountZ
        {
            get { return vertexCountZ; }
            set { vertexCountZ = value; }
        }
        /// <summary>
        /// Horizontal vertex scale.
        /// </summary>
        private float blockScale = 1.0f;
        /// <summary>
        /// Gets horizontal vertex scale.
        /// </summary>
        public float BlockScale
        {
            get { return blockScale; }
        }
        /// <summary>
        /// Vertical vertex scale
        /// </summary>
        private float heightScale = 1.0f;
        /// <summary>
        /// Gets vertical vertex scale.
        /// </summary>
        public float HeightScale
        {
            get { return heightScale; }
        }
        /// <summary>
        /// Vertices count.
        /// </summary>
        private int vertexCount = 0;
        /// <summary>
        /// Gets vertex count.
        /// </summary>
        public int VertexCount
        {
            get { return vertexCount; }
        }
        /// <summary>
        /// Triangle count.
        /// </summary>
        private int triangleCount = 0;
        /// <summary>
        /// Gets triangle count.
        /// </summary>
        public int TriangleCount
        {
            get { return triangleCount; }
        }

        private Color[] heightMap;

        #endregion

        #region Constructor

        public Terrain()
        {

        }

        #endregion

        #region Loader

        public void Load()
        {
            vertexCountX = 256;
            vertexCountZ = 256;
            blockScale = 1.0f;
            heightScale = 1.0f;

            vertexCount = vertexCountX * vertexCountZ;
            triangleCount = (vertexCountX - 1) * (vertexCountZ - 1) * 2;

            heightMap = new Color[vertexCount];
            // todo load heightmap data

            int[] indices = GenerateTerrainIndices();
        }

        private int[] GenerateTerrainIndices()
        {
            int numIndices = triangleCount * 3;
            int[] indices = new int[numIndices];
            int indicesCount = 0;
            for (int i = 0; i < (vertexCountZ - 1); i++)
            {
                for (int j = 0; j < (vertexCountX - 1); j++)
                {
                    int index = j + i * vertexCountZ;
                    // First triangle
                    indices[indicesCount++] = index;
                    indices[indicesCount++] = index + 1;
                    indices[indicesCount++] = index + vertexCountX + 1;
                    // Second triangle
                    indices[indicesCount++] = index + vertexCountX + 1;
                    indices[indicesCount++] = index + vertexCountX;
                    indices[indicesCount++] = index;
                }
            }
            return indices;
        }

        private VertexPositionNormalTangentBinormalTexture[] GenerateTerrainVertices(int[] terrainIndices)
        {
            float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f;
            float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f;

            // Texture coordinates
            float tu = 0;
            float tv = 0;
            float tuDerivative = 1.0f / (vertexCountX - 1);
            float tvDerivative = 1.0f / (vertexCountZ - 1);
            int vertexCount = 0;

            VertexPositionNormalTangentBinormalTexture[] vertices = new VertexPositionNormalTangentBinormalTexture[vertexCountX * vertexCountZ];

            // Set vertices position and texture coordinate
            for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale)
            {
                tu = 0.0f;
                for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale)
                {
                    vertices[vertexCount].Position =
                        new Vector3(j, heightMap[vertexCount].R * heightMap[vertexCount].G * heightMap[vertexCount].B * heightScale, i);

                    vertices[vertexCount].TextureCoordinate =
                        new Vector2(tu, tv);

                    tu += tuDerivative;
                    vertexCount++;
                }
                tv += tvDerivative;
            }
            // Generate vertice's normal, tangent and binormal
            GenerateTerrainNormals(vertices, terrainIndices);
            GenerateTerrainTangentBinormal(vertices, terrainIndices);
            return vertices;
        }

        private void GenerateTerrainNormals(VertexPositionNormalTangentBinormalTexture[] vertices, int[] indices)
        {
            for (int i = 0; i < indices.Length; i += 3)
            {
                // Get the vertex position (v1, v2, and v3)
                Vector3 v1 = vertices[indices[i]].Position;
                Vector3 v2 = vertices[indices[i + 1]].Position;
                Vector3 v3 = vertices[indices[i + 2]].Position;

                // Calculate vectors v1->v3 and v1->v2 and the normal as a cross product
                Vector3 vu = v3 - v1;
                Vector3 vt = v2 - v1;
                Vector3 normal = Vector3.Cross(vu, vt);
                normal.Normalize();

                // Sum this normal with the current vertex normal of the three vertices
                vertices[indices[i]].Normal += normal;
                vertices[indices[i + 1]].Normal += normal;
                vertices[indices[i + 2]].Normal += normal;
            }
            // After calculating all the normals, normalize them
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();
        }

        public void GenerateTerrainTangentBinormal(VertexPositionNormalTangentBinormalTexture[] vertices, int[] indices)
        {
            for (int i = 0; i < vertexCountZ; i++)
            {
                for (int j = 0; j < vertexCountX; j++)
                {
                    int vertexIndex = j + i * vertexCountX;
                    Vector3 v1 = vertices[vertexIndex].Position;
                    // Calculate the tangent vector
                    if (j < vertexCountX - 1)
                    {
                        Vector3 v2 = vertices[vertexIndex + 1].Position;
                        vertices[vertexIndex].Tangent = (v2 - v1);
                    }
                    // Special case: last vertex of the plane in the X axis
                    else
                    {
                        Vector3 v2 = vertices[vertexIndex - 1].Position;
                        vertices[vertexIndex].Tangent = (v1 - v2);
                    }
                    // Calculate binormal as a cross product (Tangent x Normal)
                    vertices[vertexIndex].Tangent.Normalize();
                    vertices[vertexIndex].Binormal = Vector3.Cross(
                    vertices[vertexIndex].Tangent, vertices[vertexIndex].Normal);
                }
            }
        }
        
        #endregion
    }
}
